package ru.compscicenter.databases.zaikina.common;

import ru.compscicenter.databases.zaikina.common.model.Word;
import ru.compscicenter.databases.zaikina.common.model.WordWithReferences;

import java.util.*;

public class Vocabulary implements Cloneable {
    //private final String name;// = "vocabulary";
    private final Integer nodeId;

    private final IOProcessor<WordWithReferences> io = new IOProcessor<WordWithReferences>() {
        @Override
        protected WordWithReferences parseItem(String str) {
            return WordWithReferences.parse(str);
        }
    };

    private final PathGenerator fileNameGenerator;

    private HashMap<String, ArrayList<Integer>> oneFirstLetter = new HashMap<String, ArrayList<Integer>>();
    private HashMap<String, ArrayList<Integer>> twoFirstLetters = new HashMap<String, ArrayList<Integer>>();
    private HashMap<String, ArrayList<Integer>> threeFirstLetters = new HashMap<String, ArrayList<Integer>>();
    private HashMap<String, ArrayList<Integer>> words = new HashMap<String, ArrayList<Integer>>();

    private HashMap<Integer, WordWithReferences> wordsObjects = new HashMap<Integer, WordWithReferences>();

    /*private Vocabulary(String name) {
        this.name = name;
        this.fileNameGenerator = new PathGenerator(name);
    }*/

    public Vocabulary(Integer nodeId) {
        this.nodeId = nodeId;
        this.fileNameGenerator = new PathGenerator(nodeId);
    }

    private void addItem(WordWithReferences item) {
        String word = item.getObj().getKey();
        int id = wordsObjects.size();

        if (word.length() > 0) {
            addToHash(id, word.substring(0, 1), oneFirstLetter);
        }

        if (word.length() > 1) {
            addToHash(id, word.substring(0, 2), twoFirstLetters);
        }

        if (word.length() > 2) {
            addToHash(id, word.substring(0, 3), threeFirstLetters);
        }

        addToHash(id, word, words);
        wordsObjects.put(id, item);
    }

    public void add(ArrayList<WordWithReferences> newWords) {
        for (WordWithReferences item : newWords) {
            if (item == null) {
                continue;
            }

            if (words.containsKey(item.getObj().getKey())) {
                int idx = words.get(item.getObj().getKey()).get(0);
                wordsObjects.get(idx).mergeWith(item);
            } else {
                addItem(item);
            }
        }
    }

    public void remove(ArrayList<WordWithReferences> items) {
        for (WordWithReferences item : items) {
            if (words.containsKey(item.getObj().getKey())) {
                int idx = words.get(item.getObj().getKey()).get(0);
                wordsObjects.get(idx).subtract(item);
                if (wordsObjects.get(idx).getReferencesCount() == 0) {
                    words.remove(item.getObj().getKey());
                    wordsObjects.remove(idx);
                }
            }
        }
    }

    public void update(ArrayList<WordWithReferences> items) {
        for (WordWithReferences item : items) {
            if (words.containsKey(item.getObj().getKey())) {
                int idx = words.get(item.getObj().getKey()).get(0);
                wordsObjects.get(idx).update(item);
            }
        }
    }

    public void commit() {
        int version = io.readVersion(fileNameGenerator.versionPath()) + 1;
        io.writeObjectsToDump(fileNameGenerator.fullObjectsPath(version), wordsObjects);
        io.writeHashToDump(fileNameGenerator.fullWordPath(version), words);
        io.writeHashToDump(fileNameGenerator.oneFirstLetterPath(version), oneFirstLetter);
        io.writeHashToDump(fileNameGenerator.twoFirstLettersPath(version), twoFirstLetters);
        io.writeHashToDump(fileNameGenerator.threeFirstLettersPath(version), threeFirstLetters);
        io.writeVersion(fileNameGenerator.versionPath(), version);
    }

    public void load() {
        int version = io.readVersion(fileNameGenerator.versionPath());

        wordsObjects = io.loadObjectsFromDump(fileNameGenerator.fullObjectsPath(version));
        /*int idx = 0;
        for (WordWithReferences item : items) {
            if (item != null) {
                wordsObjects.put(idx, item);
            }
            ++idx;
        }*/

        words = io.loadHashFromDump(fileNameGenerator.fullWordPath(version));
        oneFirstLetter = io.loadHashFromDump(fileNameGenerator.oneFirstLetterPath(version));
        twoFirstLetters = io.loadHashFromDump(fileNameGenerator.twoFirstLettersPath(version));
        threeFirstLetters = io.loadHashFromDump(fileNameGenerator.threeFirstLettersPath(version));
    }

    /*public ArrayList<WordWithReferences> get(ArrayList<WordWithReferences> items) {
        ArrayList<WordWithReferences> result = new ArrayList<WordWithReferences>();
        for (WordWithReferences item : items) {
            result.addAll(get(item.getObj().getKey()));
        }
        return result;
    }
*/
    public ArrayList<Word> get(ArrayList<String> items) {
        ArrayList<Word> result = new ArrayList<Word>();
        for (String item : items) {
            ArrayList<WordWithReferences> arr = get(item);
            for (WordWithReferences word : arr) {
                result.add(word.getObj());
            }
        }
        return result;
    }

    public ArrayList<WordWithReferences> get(String word) {
        if (word == null || word.equals("")) {
            return new ArrayList<WordWithReferences>();
        }

        word = word.toUpperCase();
        if (word.length() == 1) {
            return findInHashMap(oneFirstLetter, word);
        }

        if (word.length() == 2) {
            return findInHashMap(twoFirstLetters, word);
        }

        if (word.length() == 3) {
            return findInHashMap(threeFirstLetters, word);
        }

        ArrayList<WordWithReferences> result = findInHashMap(words, word);
        ArrayList<WordWithReferences> wordArr = findInHashMap(threeFirstLetters, word.substring(0, 3));
        for (WordWithReferences item : wordArr) {
            if (item.getObj().getKey().startsWith(word) && item.getObj().getKey().length() != word.length()) {
                result.add(item);
            }
        }

        return result;
    }


    private ArrayList<WordWithReferences> findInHashMap(HashMap<String, ArrayList<Integer>> map, String word) {
        if (map.containsKey(word)) {
            List<Integer> ids = map.get(word);
            ArrayList<WordWithReferences> answer = new ArrayList<WordWithReferences>();
            for (Integer id : ids) {
                answer.add(wordsObjects.get(id));
            }
            return answer;
        }
        return new ArrayList<WordWithReferences>();
    }

    private <T> void addToHash(int id, final T key, HashMap<T, ArrayList<Integer>> hashMap) {
        if (hashMap.containsKey(key)) {
            hashMap.get(key).add(id);
            return;
        }

        ArrayList<Integer> ids = new ArrayList<Integer>();
        ids.add(id);
        hashMap.put(key, ids);
    }


    private class IdComparator implements Comparator<Integer> {

        public int compare(Integer o1, Integer o2) {
            if (o1 >= wordsObjects.size() || o2 >= wordsObjects.size()) {
                return 0;
            }

            //return Integer.compare(wordsObjects.get(o2).getReferencesCount()
            //        , wordsObjects.get(o1).getReferencesCount());
            int item1 = wordsObjects.get(o1).getReferencesCount();
            int item2 = wordsObjects.get(o2).getReferencesCount();
            if (item1 > item2) {
		return -1;
	    }
            return 1;
        }

        public boolean equals(Object obj) {
            return false;
        }
    }

    public void normalize() {
        normalizeMap(oneFirstLetter);
        normalizeMap(twoFirstLetters);
        normalizeMap(threeFirstLetters);
    }

    private void normalizeMap(HashMap<String, ArrayList<Integer>> map) {
        for (String key : map.keySet()) {
            Collections.sort(map.get(key), new IdComparator());
        }
    }

    public Vocabulary clone() {
        Vocabulary copy = new Vocabulary(nodeId);
        copy.oneFirstLetter = copy(oneFirstLetter);
        copy.twoFirstLetters = copy(twoFirstLetters);
        copy.threeFirstLetters = copy(threeFirstLetters);
        copy.words = copy(words);
        copy.wordsObjects = new HashMap<Integer, WordWithReferences>();
        for (Integer key : wordsObjects.keySet()) {
            copy.wordsObjects.put(new Integer(key.intValue()), wordsObjects.get(key).clone());
        }
        return copy;
    }

    private HashMap<String, ArrayList<Integer>> copy(HashMap<String, ArrayList<Integer>> map) {
        HashMap<String, ArrayList<Integer>> mapCopy = new HashMap<String, ArrayList<Integer>>();
        for (String key : map.keySet()) {
            mapCopy.put(key, copy(map.get(key)));
        }
        return mapCopy;
    }

    private ArrayList<Integer> copy(ArrayList<Integer> list) {
        ArrayList<Integer> listCopy = new ArrayList<Integer>();
        for (Integer item : list) {
            listCopy.add(new Integer(item.intValue()));
        }
        return listCopy;
    }
}
